<template>
  <div id="container" class="g6-container">
    <el-button-group
      id="toolbar"
      class="g6-component-toolbar"
      key="scale-control"
    >
      <slot name="buttonLeft"></slot>
      <el-tooltip
        code="layout"
        class="item"
        effect="light"
        content="布局"
        placement="bottom"
      >
        <el-button size="small" icon="el-icon-s-tools" @click="onLayout" />
      </el-tooltip>
      <el-tooltip class="item" effect="light" content="放大" placement="bottom">
        <el-button
          code="zoomIn"
          size="small"
          icon="el-icon-zoom-in"
          @click="onZoomIn"
        />
      </el-tooltip>
      <el-tooltip class="item" effect="light" content="缩小" placement="bottom">
        <el-button
          code="zoomOut"
          size="small"
          icon="el-icon-zoom-out"
          @click="onZoomOut"
        />
      </el-tooltip>
      <el-tooltip class="item" effect="light" content="新增" placement="bottom">
        <el-button size="small" icon="el-icon-files" @click="addTable" />
      </el-tooltip>
      <el-tooltip
        class="item"
        effect="light"
        content="删除"
        placement="bottom"
        v-if="remove === true"
      >
        <el-button size="small" icon="el-icon-delete" @click="removeTable" />
      </el-tooltip>
      <el-tooltip
        class="item"
        effect="light"
        content="导出PNG"
        placement="bottom"
      >
        <el-button
          size="small"
          icon="el-icon-picture-outline"
          @click="exportPNGFn"
        />
      </el-tooltip>
      <el-input-number
        v-model="nodeNum"
        :min="10"
        :max="1000"
        label="描述文字"
      ></el-input-number>
      <slot name="buttonRight"></slot>
    </el-button-group>

    <div id="miniMapContainer" class="miniMap"></div>
  </div>
</template>

<script>
import G6 from '@antv/g6';
const {
  Util,
  registerBehavior,
  registerEdge,
  registerNode
} = G6;
export default {
  props: {
    create: {
      type: Boolean,
      default() {
        return false
      }
    },
    remove: {
      type: Boolean,
      default() {
        return false
      }
    },
    connect: {
      type: Boolean,
      default() {
        return false
      }
    }
  },
  data() {
    return {
      sourceAnchor: '',
      count: 0,
      nodeNum: 10
    }
  },
  watch: {
    nodeNum(e) {
      if (this.graph) {
        // this.graph.clear()
        // this.graph.changeData(this.dataTransform(this.initData(e))
        this.graph.data(this.dataTransform(this.initData(e)))
        this.graph.render()
      }
    }
  },
  mounted() {
    const vm = this

    this.customRegisterScrollBehavior()
    this.customRegisterNode(vm)
    this.customRegisterEdge(vm)

    let source, target, sourceAnchor, targetAnchor;

    const container = document.getElementById('container');

    const width = container.scrollWidth;
    const height = container.scrollHeight || 500;
    const grid = new G6.Grid();
    const minimap = new G6.Minimap({
      className: 'miniMap',
      size: [300, 200]
    })
    // 工具栏
    const toolbar = new G6.ToolBar({
      className: 'g6-component-toolbar',
      getContent: () => {
        // 渲染自定义节点---组件
        const outEl = document.getElementById('toolbar')
        return outEl

      },
      position: { x: 10, y: 10 }
    })
    const graph = new G6.Graph({
      container: 'container',
      width,
      height,
      minZoom: 0.5,
      maxZoom: 2,
      defaultNode: {
        size: [300, 400],
        type: 'dice-er-box',
        color: '#5B8FF9',
        // collapsed: true,
        boxStyle: {
          stroke: "#096DD9",
          radius: 4,
        },
        style: {
          fill: '#9EC9FF',
          lineWidth: 3,
        },
        labelCfg: {
          style: {
            fill: 'black',
            fontSize: 20,
          },
        },
      },
      defaultEdge: {
        type: 'dice-er-edge',
        style: {
          stroke: "#5B8FF9",
          lineWidth: 2,
          endArrow: true,
        },
      },
      plugins: [grid, minimap, toolbar],
      modes: {
        default: ['dice-er-scroll', 'click-select',
          {
            type: 'drag-canvas',
            enableOptimize: true, // 优化拖动
            scalableRange: 1
          },
          {
            type: 'drag-node',
            enableDelegate: true,
            enableOptimize: true,
            shouldBegin: e => {
              console.log('drag-node', e.target);
              // 避免连接处拖动整个节点
              if (e.target.get('name') === 'rect-col') return false;

              return true;
            }
          }, {
            type: 'create-edge',
            trigger: 'drag',
            shouldBegin: e => {
              // 避免从节点上的其他形状开始
              if (e.target && e.target.get('name') !== 'rect-col') return false;
              source = e.target.get('nodeId');
              sourceAnchor = e.target.get('anchorPointIdx');
              // 创建边起始点
              this.sourceAnchor = e.target.get('anchorPointIdx')
              e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle
              return true;
            },
            shouldEnd: e => {
              // 避免从节点上的其他形状结束
              if (e.target && e.target.get('name') !== 'rect-col') return false;
              if (e.target) {
                target = e.target.get('nodeId');
                targetAnchor = e.target.get('anchorPointIdx');
                e.target.set('links', e.target.get('links') + 1);  // cache the number of edge connected to this anchor-point circle
                return true;
              }
              target = undefined
              targetAnchor = undefined;
              return true;
            },
          }],
      },
      // layout: {
      //   type: 'grid',
      //   begin: [20, 20],
      //   preventOverlap: true // 防止重叠
      // },
      animate: true,
    })

    // 边创建
    graph.on('aftercreateedge', (e) => {
      console.log('aftercreateedge', e, source, target, sourceAnchor, targetAnchor);
      // 更新新添加边的sourceAnchor和targetAnchor
      graph.updateItem(e.edge, {
        source: source,
        target: target,
        sourceAnchor: sourceAnchor,
        targetAnchor: targetAnchor
      })

    });


    this.initEvents(graph)
    const rawData = this.initData()
    graph.data(this.dataTransform(rawData));

    graph.render();
    this.graph = graph

  },
  methods: {
    initEvents(graph) {
      // 节点点击
      graph.on('node:click', (evt) => {
        const { item, target } = evt;
        const targetType = target.get('type');
        const name = target.get('name');
        const model = evt.item.getModel()
        // console.log('click', item, item.hasState('selected'));
        item.toFront()
        // if(evt.originalEvent.shiftKey) 
        // graph.setItemState(item, 'selected', !item.hasState('selected')); // 切换选中


      })
      graph.on('node:dblclick', (evt) => {
        const { item, target } = evt;
        const model = evt.item.getModel()
        console.log('dblclick', model);

      })

      graph.on('edge:click', (evt) => {
        const { item, target } = evt;
        const targetType = target.get('type');
        graph.setItemState(item, 'selected', !item.hasState('selected')); // 切换选中
        console.log('edge:click', evt);
      })

      graph.on('canvas:dragstart', (evt) => {
        console.log('canvas:dragstart', evt);
      })

      // 布局后根据节点定位更新
      // graph.on('afterlayout', e => {
      //   console.log('afterlayout');
      //   // graph.updateItem(xxnode, { x: 10, y: 10 })
      // })

    },
    initData(num = this.nodeNum) {
      const nodeArray = []
      for (let i = 1; i < num; i++) {
        const obj = { id: '' + i, label: 'Employee' + i, attrs: [] }
        obj.key = 'Record' + i
        obj.attrs = [{
          nodeId: '' + i,
          key: 'id',
          type: 'number(6)',
          // img: {
          //   show: true,
          //   // the image url for the icon, string type
          //   img:
          //     'https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg',
          //   width: 16,
          //   height: 16,
          // }
        },
        {
          nodeId: '' + i,
          key: 'key',
          type: 'varchar(255)'
        },
        {
          nodeId: '' + i,
          key: 'gender',
          type: 'enum(M, F)',
          relation: [{
            key: 'id',
            nodeId: '0'
          }]
        },
        {
          nodeId: '' + i,
          key: 'birthday',
          type: 'date'
        },
        {
          nodeId: '' + i,
          key: 'hometown',
          type: 'varchar(255)'
        },
        {
          nodeId: '' + i,
          key: 'country',
          type: 'varchar(255)'
        },
        {
          nodeId: '' + i,
          key: 'nation',
          type: 'varchar(255)'
        },
        {
          nodeId: '' + i,
          key: 'jobId',
          type: 'number(3)',
          // relation: [{
          //   key: 'id',
          //   nodeId: '1'
          // }]
        },
        {
          nodeId: '' + i,
          key: 'nation1',
          type: 'varchar(255)'
        },
        {
          nodeId: '' + i,
          key: 'nation2',
          type: 'varchar(255)'
        },
        {
          nodeId: '' + i,
          key: 'nation3',
          type: 'varchar(255)'
        }
        ]
        nodeArray.push(obj)

      }
      return nodeArray
    },
    dataTransform(data) {
      const nodes = [];
      const edges = [];
      // TODO:测试node中的定位
      data.map((node) => {
        nodes.push({
          ...node
        });
        if (node.attrs) {
          node.attrs.forEach((attr) => {
            if (attr.relation) {
              attr.relation.forEach((relation) => {
                edges.push({
                  source: node.id,
                  target: relation.nodeId,
                  sourceAnchor: attr.key,
                  targetAnchor: relation.key,
                  label: relation.label,
                });
              });
            }

          });
        }
      });
      console.log('data', nodes, edges);

      return {
        nodes,
        edges,
      };
    },
    onLayout() {
      // 更新布局
      const grid = {
        type: 'grid',
        begin: [20, 20],
        preventOverlap: true // 防止重叠
      }
      // const dagre = {
      //   type: 'dagre',
      //   rankdir: 'LR',
      //   align: 'UL',
      //   controlPoints: true,
      //   nodesepFunc: () => 0.2,
      //   ranksepFunc: () => 0.5,
      // }
      this.graph.updateLayout(grid)
    },
    onZoomIn() {
      this.graph.zoom(1.2)
      this.graph.emit('afterrender')
    },
    onZoomOut() {
      this.graph.zoom(0.8)
      this.graph.emit('afterrender')
    },
    addTable() {
      this.count++
      const model = {
        id: `new${this.count}`,
        key: `cs${this.count}`,
        label: `测试${this.count}`,
        x: 100 * Math.random(1, 5),
        y: 100 * Math.random(1, 5),
        type: 'dice-er-box',
        attrs: [
          {
            nodeId: `new${this.count}`,
            key: `col${this.count}`,
            type: "varchar",
            img: {
              show: true,
              img: "https://gw.alipayobjects.com/zos/basement_prod/4f81893c-1806-4de4-aff3-9a6b266bc8a2.svg",
              width: 16,
              height: 16
            }
          },
          {
            nodeId: `new${this.count}`,
            key: `col${this.count + 1}`,
            type: "char"
          }
        ]
      }
      this.graph.addItem('node', model)
      // this.$emit('add')
    },
    removeTable() {
      this.$emit('remove')
    },
    exportPNGFn() {
      this.graph.downloadFullImage('dber', 'image/png', {
        backgroundColor: '#ddd',
        padding: [30, 15, 15, 15],
      });
    },
    customRegisterNode(vm) {
      const itemHeight = 30
      registerNode("dice-er-box", {
        draw(cfg, group) {
          // 节点容器size
          const width = 250;
          const height = 416;
          const collapseHeight = height - 16
          const itemCount = 12;
          // 边框、底色控制
          const boxStyle = cfg.boxStyle
console.log('更新节点');

          const {
            attrs = [],
            startIndex = 0,
            selectedIndex,
            collapsed,
            icon,
          } = cfg;
          const list = attrs;
          const afterList = list.slice(
            Math.floor(startIndex),
            Math.floor(startIndex + itemCount - 1)
          );
          const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30;

          group.addShape("rect", {
            attrs: {
              fill: boxStyle.stroke,
              height: 30,
              width,
              radius: [boxStyle.radius, boxStyle.radius, 0, 0],
            },
            draggable: true,
          });

          let fontLeft = 12;
          if (icon && icon.show !== false) {
            group.addShape("image", {
              attrs: {
                x: 8,
                y: 8,
                height: 16,
                width: 16,
                ...icon,
              },
            });
            fontLeft += 18;
          }
          // 节点顶部文本
          group.addShape("text", {
            attrs: {
              y: 22,
              x: fontLeft,
              fill: "#fff",
              text: cfg.label,
              fontSize: 12,
              fontWeight: 500,
            },
            draggable: true,
          });
          // 节点底部操作图形
          group.addShape("rect", {
            attrs: {
              x: 0,
              y: collapsed ? 30 : collapseHeight,
              height: 15,
              width,
              fill: "#eee",
              radius: [0, 0, boxStyle.radius, boxStyle.radius],
              cursor: "pointer",
            },
            name: collapsed ? "expand" : "collapse",
          });
          // 节点底部操作文本
          group.addShape("text", {
            attrs: {
              x: width / 2 - 6,
              y: (collapsed ? 30 : collapseHeight) + 12,
              text: collapsed ? "+" : "-",
              width,
              fill: "#000",
              radius: [0, 0, boxStyle.radius, boxStyle.radius],
              cursor: "pointer",
            },
            name: collapsed ? "expand" : "collapse",
          });

          // TODO: 根据group树计算height
          const keyshape = group.addShape("rect", {
            attrs: {
              x: 0,
              y: 0,
              width,
              height: collapsed ? 45 : height,
              ...boxStyle,
            },
            draggable: true,
          });

          // 收缩时
          if (collapsed) {
            return keyshape;
          }

          const listContainer = group.addGroup({});
          // 设置并返回裁剪对象
          listContainer.setClip({
            type: "rect",
            attrs: {
              x: -8,
              y: 30,
              width: width + 16,
              height: collapseHeight - 30,
            },
          });
          // group部分图形控制
          listContainer.addShape({
            type: "rect",
            attrs: {
              x: 1,
              y: 30,
              width: width - 2,
              height: collapseHeight - 30,
              fill: "#fff"
            },
            draggable: true, // 该图形分组是否允许被拖拽
          });

          if (list.length > itemCount) {
            // 滚动条部分
            const barStyle = {
              width: 4,
              padding: 0,
              boxStyle: {
                stroke: "#00000022",
              },
              innerStyle: {
                fill: "#00000022",
              },
            };

            listContainer.addShape("rect", {
              attrs: {
                y: 30,
                x: width - barStyle.padding - barStyle.width,
                width: barStyle.width,
                height: height - 30,
                ...barStyle.boxStyle
              },
            });

            const indexHeight =
              afterList.length > itemCount ?
                (afterList.length / list.length) * height :
                10;

            listContainer.addShape("rect", {
              attrs: {
                y: 30 +
                  barStyle.padding +
                  (startIndex / list.length) * (height - 30),
                x: width - barStyle.padding - barStyle.width,
                width: barStyle.width,
                height: Math.min(height, indexHeight),
                ...barStyle.innerStyle,
              },
            });
          }

          if (afterList) {
            // console.log('afterList', afterList);
            afterList.forEach((e, i) => {
              const isSelected =
                Math.floor(startIndex) + i === Number(selectedIndex);
              let {
                key = "", type, img
              } = e;
              if (type) {
                key += " - " + type;
              }
              const label = key.length > 26 ? key.slice(0, 24) + "..." : key;

              // group部分图形控制
              listContainer.addShape("rect", {
                attrs: {
                  x: 1,
                  y: i * itemHeight - itemHeight / 2 + offsetY,
                  width: width - 4,
                  height: itemHeight,
                  radius: 2,
                  lineWidth: 1,
                  cursor: "pointer",
                  fill: '#fff'
                },
                name: 'rect-col',
                nodeId: e.nodeId,
                anchorPointIdx: e.key,
                links: 0,
                draggable: true,
              });

              // group文本控制
              listContainer.addShape("text", {
                attrs: {
                  x: 12,
                  y: i * itemHeight + offsetY + 6,
                  text: label,
                  fontSize: 12,
                  fill: "#000",
                  fontFamily: "Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol",
                  full: e,
                  fontWeight: isSelected ? 500 : 100,
                  cursor: "pointer",
                },
                name: 'rect-col',
                nodeId: e.nodeId,
                anchorPointIdx: e.key,
                links: 0,
                draggable: true,
              })
              // 主键图标控制
              if (img && img.show !== false) {
                listContainer.addShape("image", {
                  attrs: {
                    x: width - 26,
                    y: itemHeight + 4,
                    r: 3,
                    height: 16,
                    width: 16,
                    ...img,
                  },
                });
              }

            });
          }

          return keyshape;
        },
        getAnchorPoints() {
          return [
            [0, 0],
            [1, 0],
          ];
        },
        setState(name, value, item) {
          if (name === 'selected') {
            const group = item.getContainer();
            console.log('s', item, item.getContainer());
            const nodeId = item.getModel().id
            const shape = group.get('children')[0]; // 顺序根据 draw 时确定
            if (value) {
              shape.attr('fill', 'red');
            } else {
              shape.attr('fill', '#096DD9');
            }
          }

        }

      });
    },
    customRegisterEdge(vm) {
      registerEdge("dice-er-edge", {
        draw(cfg, group) {
          const edge = group.cfg.item;
          const sourceNode = edge.getSource()?.getModel();
          const startPoint = {
            ...cfg.startPoint
          };
          const endPoint = {
            ...cfg.endPoint
          };

          let sourceY = 15;
          let targetY = 15;
          if (edge.getTarget()?._cfg) {
            const targetNode = edge.getTarget().getModel();
            // console.log('已到达---目标', targetNode.key, targetNode);
            const sourceIndex = sourceNode.attrs.findIndex(
              (e) => e.key === cfg.sourceAnchor
            );
console.log('更新关系');
            const sourceStartIndex = sourceNode.startIndex || 0;
            // console.log('已到达---来源', cfg.sourceAnchor, sourceIndex, sourceStartIndex, sourceNode.key, sourceNode);

            if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) {
              sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * 30;
              sourceY = Math.min(sourceY, 300);
            }

            const targetIndex = targetNode.attrs.findIndex(
              (e) => e.key === cfg.targetAnchor
            );

            const targetStartIndex = targetNode.startIndex || 0;

            if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) {
              targetY = (targetIndex - targetStartIndex + 0.5) * 30 + 30;
              targetY = Math.min(targetY, 300);
            }

            startPoint.y = startPoint.y + sourceY;
            endPoint.y = endPoint.y + targetY;

            let shape;
            if (sourceNode?.id !== targetNode.id) {
              shape = group.addShape("path", {
                attrs: {
                  stroke: "#5B8FF9",
                  path: [
                    ["M", startPoint.x, startPoint.y],
                    [
                      "C",
                      endPoint.x / 3 + (2 / 3) * startPoint.x,
                      startPoint.y,
                      endPoint.x / 3 + (2 / 3) * startPoint.x,
                      endPoint.y,
                      endPoint.x,
                      endPoint.y,
                    ],
                  ],
                  lineWidth: 2,
                  endArrow: true,
                  cursor: "pointer",
                },
                name: "path-shape",
              });
            } else if (!sourceNode.collapsed) {
              let gap = Math.abs((startPoint.y - endPoint.y) / 3);
              if (startPoint["index"] === 1) {
                gap = -gap;
              }
              shape = group.addShape("path", {
                attrs: {
                  stroke: "#5B8FF9",
                  path: [
                    ["M", startPoint.x, startPoint.y],
                    [
                      "C",
                      startPoint.x - gap,
                      startPoint.y,
                      startPoint.x - gap,
                      endPoint.y,
                      startPoint.x,
                      endPoint.y,
                    ],
                  ],
                  lineWidth: 2,
                  endArrow: true,
                },
                name: "path-shape",
              });
            }


            return shape;
          } else {
            // 连线中途
            let shape;
            const sourceIndex = sourceNode.attrs.findIndex(
              (e) => e.key === vm.sourceAnchor
            );
            const sourceStartIndex = sourceNode.startIndex || 0;
            // console.log('未到达---', cfg, vm, sourceNode);
            if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) {
              sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * 30;
              sourceY = Math.min(sourceY, 300);
            }
            startPoint.y = startPoint.y + sourceY;
            endPoint.y = endPoint.y + targetY;
            // 
            shape = group.addShape("path", {
              attrs: {
                stroke: "#5B8FF9",
                path: [
                  ["M", startPoint.x, startPoint.y], // 连线起始点
                  [
                    "C",
                    endPoint.x / 3 + (2 / 3) * startPoint.x,
                    startPoint.y,
                    endPoint.x / 3 + (2 / 3) * startPoint.x,
                    endPoint.y,
                    endPoint.x,
                    endPoint.y,
                  ],
                ],
                lineWidth: 2,
                endArrow: true,
              },
              name: "path-shape",
            });
            return shape;
          }

        },
//         afterDraw(cfg, group) {
//           const labelCfg = cfg.labelCfg || {};
//           const edge = group.cfg.item;
//           const sourceNode = edge.getSource().getModel();
//           // console.log('afterDraw-未到达---', cfg);
//           if (edge.getTarget()?._cfg) {
//             const targetNode = edge?.getTarget().getModel();

//             if (sourceNode.collapsed && targetNode.collapsed) {
//               return;
//             }
//             const path = group.find(
//               (element) => element.get("name") === "path-shape"
//             );
// console.log('path-afterDraw');


//             const labelStyle = Util.getLabelPosition(path, 0.5, 0, 0, true);
//             const label = group.addShape("text", {
//               attrs: {
//                 ...labelStyle,
//                 text: cfg.label || '',
//                 fill: "#000",
//                 textAlign: "center",
//                 stroke: "red",
//                 lineWidth: 1,
//               },
//             });
//             label.rotateAtStart(labelStyle.rotate);
//           }

//         },
        setState(name, value, item) {
          if (name === 'selected') {
            const group = item.getContainer();
            const shape = group.get('children')[0]; // 顺序根据 draw 时确定
            if (value) {
              shape.attr('stroke', 'red');
            } else {
              shape.attr('stroke', '#096DD9');
            }
          }

        }


      });
    },
    customRegisterScrollBehavior() {
      registerBehavior('dice-er-scroll', {
        getDefaultCfg() {
          return {
            multiple: true, // 允许多选
          };
        },
        getEvents() {
          return {
            itemHeight: 50,
            wheel: 'scorll',
            click: 'click',
            // 'node:mouseover': 'move',
          };
        },
        click(e) {
          const {
            graph
          } = this;
          const {
            y
          } = e;
          const item = e.item;
          const shape = e.shape;
          if (!item) {
            return;
          }
          const model = item.getModel();

          if (shape.get('name') === 'collapse') {
            graph.updateItem(item, {
              collapsed: true,
              size: [300, 50]
            });
            // setTimeout(() => graph.layout(), 100);
          } else if (shape.get('name') === 'expand') {
            graph.updateItem(item, {
              collapsed: false,
              size: [300, 500],
            });
            // setTimeout(() => graph.layout(), 100);
          }
        },
        scorll(e) {
          const x = e.clientX
          const y = e.clientY
          if (e.altKey) {
            if (e.wheelDelta > 0) {
              this.graph.zoom(1.2, { x, y })
            } else {
              this.graph.zoom(0.8, { x, y })
            }
          } else {
            this.graph.translate(0, e.wheelDelta < 0 ? -100 : 100);
          }

        },
        // move(e) {
        //   console.log('move', e);
        // },
      });
    }



  }
}
</script>

<style lang="scss">
.g6-container {
  width: 98%;
  height: 98%;
  position: absolute;
  overflow: hidden;
  z-index: 0;
}
// minimap
.g6-minimap-container {
  border: 1px solid #e2e2e2 !important;
}
.g6-minimap-viewport {
  border: 2px solid rgb(25, 128, 255) !important;
}
.miniMap {
  position: relative;
  top: -200px;
}
</style>
